package applets;

import java.applet.Applet;
import java.awt.*;
import java.awt.event.*;
import java.util.ArrayList;

import bots.Bot;
import bots.Unit;

import utils.*;
import weapons.Bullet;
import surface.*;

/**
 * Very simple applet to handle fundamentals of A.I. in games.
 * 
 * This is the main applet, it handles a "battle field" with objects on it,
 * Every frames the whole field is paint again, there is a (simple) GUI.
 * 
 * How it works? After initialization of Surface, Bots, ... the applet enters
 * (in run()) an infinite loop that... just sleep to wait for next frame, and
 * then call updateBots(), then updateBelettes() then repaint(). The first and
 * second calls update positions and animations of bots and bullets, the last
 * one simple repaint all the field from scratch.
 * 
 * You may want to extend this applet using openGL like JOGL in order to enter
 * the third dimension A very simple entry for this would be for instance
 * http://jsolutions.se/DukeBeanEm
 * 
 * @author L. Simon, Univ. Paris Sud, 2008
 * 
 */

public class BattleField extends Applet implements Runnable, MouseListener, MouseMotionListener, KeyListener {
	/**
	 * 
	 */
	private static final long serialVersionUID = 1L;

	Surface surface; // The surface that contains the objects...

	// Those constants are hard constants... Why? I don't know.
	static final public float MAXX = 1000; // Size of the battlefield, in
												// float (not pixels)
	static final public float MAXY = 600;

	static final public int PREF_VIEWER_XSIZE = 1000; // size in pixels (in x,
														// the y is
														// automatically
														// deduced)

	// Viewer variables
	float viewer_scale; // Ratio from size of surface to size of viewer
	int viewer_xsize;
	int viewer_ysize;

	// Canvas for double buffering
	Image buffer_canvasimage;
	Graphics buffer_canvas; // Where to draw (off-screen buffer)
	Graphics viewer_canvas; // What the user actually see (on-screen buffer)

	ArrayList<Bot> bots;
	ArrayList<Bullet> bullets;

	/**
	 * Thread that sleeps and update the screen.
	 */
	private Thread update;

	// Very simple constructor
	public BattleField() {
		viewer_scale = MAXX / PREF_VIEWER_XSIZE;
	}

	public void init() {
		super.init();

		viewer_xsize = PREF_VIEWER_XSIZE; // size in pixels
		viewer_ysize = (int) (MAXY / viewer_scale); // The y axe is
													// automatically computed

		resize(viewer_xsize, viewer_ysize);
		buffer_canvasimage = createImage(viewer_xsize, viewer_ysize);
		buffer_canvas = buffer_canvasimage.getGraphics();
		viewer_canvas = this.getGraphics();

		addMouseListener(this);
		addMouseMotionListener(this);
		addKeyListener(this);

		initSurface();
		initBots();
		initBelettes();
	}

	/**
	 * Called ones to init the surface. This is where all objects attached to
	 * the surface should be loaded. Dynamic objects like bots and bullet are
	 * handled elsewhere.
	 */
	public void initSurface() {
		surface = new Surface(viewer_xsize, viewer_ysize, viewer_scale);
	}

	/**
	 * Called ones to init all your bots.
	 */
	public void initBots() {
		bots = new ArrayList<Bot>();
		
		// Equipe rouge :
		// Commandant dans un lance missiles
		Unit commanderRed = new Unit(null, Color.RED, 150, 5, 750, 550, 1, 1, surface, 400, 400, 100, 50, 5);
		bots.add(commanderRed);
		
		// Fantasins
		bots.add(new Unit(commanderRed, Color.RED, 50, 3, 760, 554, 0.8f, 0.5f, surface, 80, 150, 1, 1, 20));
		bots.add(new Unit(commanderRed, Color.RED, 50, 3, 765, 555, 0.8f, 0.5f, surface, 80, 150, 1, 1, 20));
		
		// Snipers
		bots.add(new Unit(commanderRed, Color.RED, 20, 3, 760, 554, 0.8f, 0.5f, surface, 200, 400, 20, 5, 50));
		bots.add(new Unit(commanderRed, Color.RED, 20, 3, 765, 555, 0.8f, 0.5f, surface, 200, 400, 20, 5, 50));
		
		// Tanks
		bots.add(new Unit(commanderRed, Color.RED, 100, 5, 751, 551, 1, 0.3f, surface, 150, 200, 30, 10, 10));
		bots.add(new Unit(commanderRed, Color.RED, 100, 5, 752, 552, 1, 0.3f, surface, 150, 200, 30, 10, 10));
		
		// Equipe bleue :
		// Commandant dans un tank
		Unit commanderBlue = new Unit(null, Color.BLUE, 150, 5, 100, 100, 1, 1, surface, 400, 400, 100, 50, 5);
		bots.add(commanderBlue);
		
		// Fantasins
		bots.add(new Unit(commanderBlue, Color.BLUE, 50, 3, 110, 100, 0.8f, 0.5f, surface, 80, 150, 1, 1, 20));
		bots.add(new Unit(commanderBlue, Color.BLUE, 50, 3, 120, 100, 0.8f, 0.5f, surface, 80, 150, 1, 1, 20));
		
		// Snipers
		bots.add(new Unit(commanderBlue, Color.BLUE, 20, 3, 100, 110, 0.8f, 0.5f, surface, 200, 400, 20, 5, 50));
		bots.add(new Unit(commanderBlue, Color.BLUE, 20, 3, 100, 120, 0.8f, 0.5f, surface, 200, 400, 20, 5, 50));
		
		// Tanks puissants
		bots.add(new Unit(commanderBlue, Color.BLUE, 100, 5, 140, 100, 1, 0.3f, surface, 150, 200, 30, 10, 10));
		bots.add(new Unit(commanderBlue, Color.BLUE, 100, 5, 150, 100, 1, 0.3f, surface, 150, 200, 30, 10, 10));
	}

	/**
	 * Called ones to init all your belettes structures.
	 */
	public void initBelettes() {
		bullets = new ArrayList<Bullet>();
	}

	public boolean handleEvent(Event event) {
		boolean returnValue = false;
		return returnValue;
	}

	public void start() {
		if (update == null) {
			update = new Thread(this);
			update.start();
		}
	}

	public void stop() {
		update = null;
	}

	/*
	 * This is the main loop of the game. Sleeping, then updating positions then
	 * redrawing If you want a constant framerate, you should measure how much
	 * you'll have to sleep depending on the time eated by updates functions.
	 * 
	 * @see java.lang.Runnable#run()
	 */
	public void run() {
		do {
			synchronized(bullets)
			{
				updateBelettes();
			}
			synchronized(bots)
			{
				updateBots();
			}
			repaint();
			try {
				Thread.sleep(33);
			} catch (InterruptedException _ex) {
			}
		} while (true);
	}

	// Use very simple double buffering technique...
	/**
	 * This is a very simple double buffering technique. Drawing are done
	 * offscreen, in the buffer_canvasimage canvas. Ones all drawings are done,
	 * we copy the whole canvas to the actual viewed canvas, viewer_canvas. Thus
	 * the player will only see a very fast update of its window. No flickering.
	 * 
	 */
	private void showbuffer() {
		viewer_canvas.drawImage(buffer_canvasimage, 0, 0, this);
	}

	/*
	 * Called by repaint, to paint all the offscreen surface. We erase
	 * everything, then redraw each components.
	 * 
	 * @see java.awt.Container#paint(java.awt.Graphics)
	 */
	public void paint(Graphics g) {
		// 1. We erase everything
		buffer_canvas.setColor(Color.lightGray); // Background color
		buffer_canvas.fillRect(0, 0, viewer_xsize, viewer_ysize);

		// 2. We draw the surface (and its objects)
		surface.draw(buffer_canvas, pointA, pointB);
		buffer_canvas.setColor(Color.black);
		buffer_canvas.drawRect(0, 0, viewer_xsize - 1, viewer_ysize - 1);

		// 3. Draw the bots in their position/direction
		synchronized(bots)
		{
			for (Bot bot : bots) {
				bot.draw(buffer_canvas);
			}
		}

		// 4. Draw the bullets / Special Effects.
		synchronized(bullets)
		{
			for (Bullet bullet : bullets) {
				bullet.draw(buffer_canvas);
			}
		}
		
		showbuffer();
	}

	/**
	 * Must update bullets positions and handles damages to bots...
	 */
	public void updateBelettes() {
		// update bullets
		ArrayList<Bullet> bulletsRemoved = new ArrayList<Bullet>();
		for (Bullet bullet : bullets) {
			bullet.update(bots, bullets);
			
			if(bullet.remove())
				bulletsRemoved.add(bullet);
		}
		
		for (Bullet bullet : bulletsRemoved) {
			bullets.remove(bullet);
		}
	}

	/**
	 * Must update bots position / decisions / health This is where your AI will
	 * be called.
	 * 
	 */
	public void updateBots() {
		// You have to update all your bots here.
		ArrayList<Bot> botsRemoved = new ArrayList<Bot>();
		for (Bot bot : bots) {
			if(bot.getHp() <= 0)
				botsRemoved.add(bot);
			else
				bot.update(bots, bullets);
		}
		
		for (Bot bot : botsRemoved) {
			bots.remove(bot);
		}
	}

	// Simply repaint the battle field... Called every frame...
	public void update(Graphics g) {
		paint(g);
	}

	public static void main(String args[]) {
		Frame f = new Frame();
		BattleField app = new BattleField();
		app.init();
		app.start();
		f.add("Center", app);
	}

	// Two point2D to memorize mouse gestures (pointA first click, pointB second
	// click)
	private Vector3 pointA = new Vector3(-1, -1, 0);
	private Vector3 pointB = new Vector3(-1, -1, 0);

	// Those methods have to be there... Even if they are empty.
	public void mouseClicked(MouseEvent e) {
	}

	public void mouseEntered(MouseEvent e) {
	}

	public void mouseExited(MouseEvent e) {
	}

	public void mousePressed(MouseEvent e) {
	}

	public void mouseDragged(MouseEvent e) {
	}

	/*
	 * Here we memorize the mouse position to draw lines where points can see
	 * eachother. TODO: you must handle mouse events in your game.
	 * 
	 * @see
	 * java.awt.event.MouseListener#mouseReleased(java.awt.event.MouseEvent)
	 */
	public void mouseReleased(MouseEvent e) {
		pointA.x = e.getX();
		pointA.y = e.getY();
	}

	/*
	 * TODO: use this method your own way.
	 * 
	 * @see
	 * java.awt.event.MouseMotionListener#mouseMoved(java.awt.event.MouseEvent)
	 */
	public void mouseMoved(MouseEvent e) {
		if (pointA.x > -1) { // pointA has been defined
			pointB.x = e.getX();
			pointB.y = e.getY();
		}
	}

	@Override
	public void keyPressed(KeyEvent e) {
	}

	@Override
	public void keyReleased(KeyEvent e) {
		if(e.getKeyCode() == KeyEvent.VK_SPACE)
			surface.log = !surface.log;
	}

	@Override
	public void keyTyped(KeyEvent e) {
	}

}
